package com.xiaopeng.common.excel;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.xiaopeng.common.enumUtil.EnumView;
import com.xiaopeng.common.enumUtil.EnumViewHandler;
import com.xiaopeng.common.exception.BusinessException;
import io.micrometer.common.util.StringUtils;
import lombok.Data;
import org.apache.poi.ss.formula.functions.T;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class CustomExcelImportUtil {


    public static List<T> readExcel(File file, Class<T> dataClazz, ExcelTemplate excelTemplate) {
        String suffix = FileNameUtil.getSuffix(file.getName());
        List<T> targetDataList = new ArrayList<>();

        CustomEasyExcelDataListener<T>  excelDataListener = new CustomEasyExcelDataListener<>((list) -> {
            targetDataList.addAll(list);
        }, dataClazz, excelTemplate);

        ExcelTypeEnum excelTypeEnum = ExcelTypeEnum.XLSX.getValue().contains(suffix) ? ExcelTypeEnum.XLSX : ExcelTypeEnum.XLS;

        // sheet所在位置索引
        int sheetIndex = Optional.ofNullable(excelTemplate.getSheetNo()).orElse(1) - 1;
        InputStream is = FileUtil.getInputStream(file);
        EasyExcel.read(is).registerReadListener(excelDataListener).ignoreEmptyRow(true)
                .excelType(excelTypeEnum).sheet(sheetIndex).autoTrim(true).doReadSync();
        return targetDataList;
    }


    @Data
    public static class ExcelTemplate {
        // 第几个sheet页
        private Integer sheetNo;

        // 表头所在行
        private Integer headRowNum;

        // excel字段映射信息
        private List<ExcelFieldMapping> excelFieldMappingList;
    }
    @Data
    public static class ExcelFieldMapping {
        // 字段名
        private String fieldName;

        // excel列名
        private String excelColumnName;
    }


    /**
     * 自定义excel数据监听器
     * @param <T>
     */
    private static class CustomEasyExcelDataListener<T> extends AnalysisEventListener<Map<Integer, String>> {

        // 标题所在行
        private Integer headRowNum = 1;

        // 当前第几行
        private Integer currentRowNum = 0;

        // 默认批次条数
        private final int BATCH_COUNT = 10000;


        // 读取的数据类型
        Class<T> dataClazz;
        // 分批读取快递信息处理方法
        Consumer<List<T>> consumer;


        ExcelTemplate excelTemplate = null;
        List<ExcelFieldMapping> excelFieldMappingList = null;

        /**
         * field字段名对应excel列所在位置索引
         */
        private final Map<String, Integer> fieldColumnIndexMap = new HashMap<>();

        /**
         * field字段名对应field
         */
        private final Map<String, Field> fieldMap = new HashMap<>();


        // 解析的信息
        List<T> list = new ArrayList<>();


        public CustomEasyExcelDataListener(Consumer<List<T>> consumer, Class<T> dataClazz, ExcelTemplate excelTemplate) {
            this.consumer = consumer;
            this.dataClazz = dataClazz;
            this.excelTemplate = excelTemplate;
            this.headRowNum = Optional.ofNullable(excelTemplate.getHeadRowNum()).orElse(1);
            this.excelFieldMappingList = excelTemplate.getExcelFieldMappingList();
            for (ExcelFieldMapping excelFieldMapping : this.excelFieldMappingList) {
                String fieldName = excelFieldMapping.getFieldName();
                fieldMap.put(fieldName, getField(dataClazz, fieldName));
            }
        }

        /**
         * 获取class字段
         * @param clazz
         * @param fieldName
         * @return
         */
        public Field getField(Class clazz, String fieldName){
            try {
                Field field =  clazz.getDeclaredField(fieldName);
                // 设置可访问性
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(clazz.getName() + "不存在字段" + fieldName);
            }
        }


        @Override
        public void onException(Exception exception, AnalysisContext context) throws Exception {

        }

        @Override
        public void invoke(Map<Integer, String> data, AnalysisContext analysisContext) {
            // 获取当前实际读取行数
            ReadRowHolder readRowHolder = analysisContext.readRowHolder();
            currentRowNum = readRowHolder.getRowIndex() + 1;
            if (currentRowNum <= headRowNum) {
                // 标题
                handleHead(data);
            } else {
                // 数据信息
                handleData(data);
            }
        }


        /**
         * 保存标题信息
         * @param currentHead<表头列索引,  表头列名>
         */
        private void handleHead(Map<Integer, String> currentHead) {
            // excel列名 -> 字段名map转化
            Map<String, String> excelColumnFiledMap = excelFieldMappingList.stream()
                    .collect(Collectors.toMap(ExcelFieldMapping::getExcelColumnName, ExcelFieldMapping::getFieldName));
            for (Map.Entry<Integer, String> entry : currentHead.entrySet()) {
                String excelColumnName = entry.getValue();
                if (StringUtils.isNotEmpty(excelColumnName)) {
                    String fieldName = excelColumnFiledMap.get(excelColumnName);
                    if (StringUtils.isNotEmpty(fieldName)) {
                        fieldColumnIndexMap.put(fieldName, entry.getKey());
                    }
                }
            }
        }

        /**
         * 处理数据
         * @param data
         */
        private void handleData(Map<Integer, String> data) {
            try {
                T targetObj = dataClazz.newInstance();
                for (ExcelFieldMapping excelFieldMapping : excelFieldMappingList) {
                    String fieldName = excelFieldMapping.getFieldName();
                    Integer columnIndex = fieldColumnIndexMap.get(fieldName);
                    String columnValue = data.get(columnIndex);
                    if (StringUtils.isEmpty(columnValue)) {
                        continue;
                    }
                    try {
                        Field field = fieldMap.get(fieldName);
                        Type fieldType = TypeUtil.getType(field);
                        EnumView enumView = field.getAnnotation(EnumView.class);
                        Object filedValue = null;
                        if (Objects.nonNull(enumView)) {
                            // 枚举转换
                            filedValue = EnumViewHandler.getCodeByName(enumView, columnValue);
                        } else {
                            // 转换为目标对象属性
                            filedValue = Convert.convert(fieldType, columnValue);
                        }
                        field.set(targetObj, filedValue);
                    } catch (Exception e) {
                        throw new RuntimeException(StrUtil.format("解析第{}行[{}]到对象属性{}异常，{}",
                                currentRowNum, excelFieldMapping.excelColumnName, fieldName, e.getMessage()));
                    }
                }
                list.add(targetObj);
            } catch (BusinessException e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException(StrUtil.format("读取Excel解析第{}行信息异常，error:{}" , currentRowNum, e.getMessage()));
            }

            if (list.size() >= BATCH_COUNT) {
                accept();
                list.clear();
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            if (CollUtil.isNotEmpty(list)) {
                accept();
                list.clear();
            }
        }

        private void accept() {
            consumer.accept(list);
        }

    }

}
